//==============================================================================
//
//         DURL - the Uniform Resource Locator class in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-07-22 13:28:57 $ $Revision: 1.4 $

//==============================================================================

#include "ofc/DURL.h"

#include <ctype.h>


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"
#include "ofc/DInt.h"

#include "ofc/DParsable.h"
#include "ofc/DTextable.h"

//
// The DURL class implements a number of methods for using URLs accordingly the
// RFC1738 specification. Note: this class is not a validator; the url:/fromString:
// methods check only a few rules. ToDo: relative urls, factory method for protocol
// client classes.
//

@interface DURL : Object <DTextable,DParsable>
{
@private
  DText            *_scheme;     // the scheme (or protocol) of the url
  DText            *_user;       // the username in the url
  BOOL              _noUser;     // there is no user name in the url
  DText            *_password;   // the password in the url
  BOOL              _noPassword; // there is no password in the url
  DText            *_host;       // the host
  DInt             *_port;       // the port number
  DText            *_path;       // the path
}

#endif


@implementation DURL


//// Constructors

//
// Initialise an empty URL
//
// @return the object
//

- (DURL *) init
{
  [super init];


  _scheme     = [DText new];
  _user       = [DText new];
  _noUser     = YES;
  _password   = [DText new];
  _noPassword = YES;
  _host       = [DText new];
  _port       = [DInt  new];
  _path       = [DText new];
  
  return self;
}

//
// Initialise the URL with an url
//
// @param url          the url
// 
// @return the object
//

- (DURL *) init :(const char *) url
{
  [self init];
  
  [self url :url];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object
//
// @return the object
//
- shallowCopy
{
  DURL *copy = [super shallowCopy];
  
  copy->_scheme   = [_scheme copy];
  copy->_user     = [_user copy];
  copy->_password = [_password copy];
  copy->_host     = [_host copy];
  copy->_path     = [_path copy];
  copy->_port     = [_port copy];
  
  return copy;
}


//// Deconstructor

//
// Free the url object
//
// @return the object
//

- free
{
  [_scheme   free];
  [_user     free];
  [_password free];
  [_host     free];
  [_path     free];
  [_port     free];
  
  return [super free];
}


//// Member methods

//
// Return the scheme
//
// @return the scheme
// 

- (const char *) scheme
{
  return [_scheme cstring];
}

//
// Return the protocol (identical to the scheme)
//
// @return the scheme (or NULL)
// 

- (const char *) protocol
{
  return [self scheme];
}

//
// Set the scheme
// 
// @param scheme   the scheme for the url (or NULL)
// 
// @return the object
// 

- (DURL *) scheme :(const char *) scheme
{
  if (scheme == NULL)
  {
    [_scheme clear];
  }
  else
  {
    [_scheme set :scheme];
  }
  
  return self;
}

//
// Return the user name
// 
// @return the user name (or NULL)
// 

- (const char *) user
{
  return (_noUser ? NULL : [_user cstring]);
}

//
// Set the user name
// 
// @param user     the user name (or NULL)
// 
// @return the object
// 

- (DURL *) user :(const char *) user
{
  _noUser = (user == NULL);
  
  if (_noUser)
  {
    [_user clear];
  }
  else
  {
    [_user set :user];
  }
  
  return self;
}

//
// Return the password
// 
// @return the password (or NULL)
// 

- (const char *) password
{
  return (_noPassword ? NULL : [_password cstring]);
}

//
// Set the password for the url
// 
// @param password the password
// 
// @return the object
// 

- (DURL *) password :(const char *) password
{
  _noPassword = (password == NULL);
  
  if (_noPassword)
  {
    [_password clear];
  }
  else
  {
    [_password set :password];
  }
  
  return self;
}

//
// Return the host
// 
// @return the host (or NULL)
// 

- (const char *) host
{
  return [_host cstring];
}

//
// Set the host
// 
// @param host     the host (or NULL)
// 
// @return the object
// 

- (DURL *) host :(const char *) host
{
  if (host == NULL)
  {
    [_host clear];
  }
  else
  {
    [_host set :host];
  }
  
  return self;
}

//
// Return the port
// 
// @return the port (or 0)
// 

- (int) port
{
  return [_port get];
}

//
// Set the port
// 
// @param port     the port (or 0)
// 
// @return the object
// 

- (DURL *) port :(int) port
{
  [_port set :port];
  
  return self;
}

//
// Return the path of the url
// 
// @return the path (or NULL)
// 

- (const char *) path
{
  return [_path cstring];
}

//
// Set the path of the url
// 
// @param path     the path (or NULL)
// 
// @return the object
// 

- (DURL *) path :(const char *) path
{
  if (path == NULL)
  {
    [_path clear];
  }
  else
  {
    [_path set :path];
  }
  
  return self;
}


//// Main methods

//
// Clear the url
// 
// @return the object
// 

- (DURL *) clear
{
  _noUser     = YES;
  _noPassword = YES;
    
  [_scheme   clear];
  [_host     clear];
  [_user     clear];
  [_password clear];
  [_path     clear];
  
  [_port     set :0];
  
  return self;
}

//
// Return the url 
// 
// @return a (new) text object with the url
//

- (DText *) url
{
  DText *url = [DText new];

  if ([_scheme length] > 0)
  {
    [url append :[_scheme cstring]];
    [url append :":"];
  }
  
  if ([_host length] > 0)
  {
    [url append :"//"];
    
    if (!_noUser)
    {
      [url append :[_user cstring]];
      
      if (!_noPassword)
      {
        [url append :":"];
        [url append :[_password cstring]];
      }
      
      [url append :"@"];
    }
    
    [url append :[_host cstring]];
    
    if ([_port get] > 0)
    {
      DText *port = [_port toText];
      
      [url append :":"];
      [url append :[port cstring]];
      
      [port free];
    }
  }
    
  [url append :[_path cstring]];

  return url;
}

//
// Set the url
// 
// @param url      the url to be set
// 
// @return success
// 

- (BOOL) url :(const char *) url
{
  BOOL ok = NO;
  
  if (url == NULL)
  {
    WARNING(DW_INVALID_ARG, "url");
  }
  else
  {
    char *str = (char *) url;
    
    ok = ([self fromString :&str] != ERANGE);
  }
  
  return ok;
}

//
// Set an url with a reference url (missing scheme, user,
// password, host and port in url are used from reference)
// 
// @param url       the url to be set
// @param reference the reference url
// 
// @return success
// 

- (BOOL) url :(const char *) url :(DURL *) reference
{
  BOOL ok = [self url :url];
  
  if ([_scheme length] == 0)
  {
    [_scheme set :[reference scheme]];
  }
  
  if ((_noUser) && ([reference user] != NULL))
  {
    _noUser = NO;
    
    [_user set :[reference user]];
  }
  
  if ((_noPassword) && ([reference password] != NULL))
  {
    _noPassword = NO;
    
    [_password set :[reference password]];
  }
  
  if ([_host length] == 0)
  {
    [_host set :[reference host]];
  }
  
  if ([_port get] == 0)
  {
    [_port set :[reference port]];
  }
    
  return ok;
}


//// Parsable protocol

//
// Parse a string to set an url
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return the result (0, ERANGE, ENODATA)
//

- (int) fromString :(char **) cstr
{
  int   result = 0;
  char *url    = *cstr;
  int   index  = 0;

  [self clear];
  
  while (isspace(url[index]))
  {
    index++;
  }
  
  if (url[index] == EOS)
  {
    result = ENODATA;
  }
  else
  {
    int separator   = -1; // ':'
    int mainSep     = -1; // '//'
    int userSep     = -1; // '@'
    int pathSep     = -1; // '/'
    int start       =  index;
    int end         =  index;
  
    while ((url[index] != EOS) && (!isspace(url[index])) && (result == 0))
    {
      // General separator
      if (url[index] == ':')
      {
        if (separator == -1)
        {
          separator = index;
        }
        else if (pathSep != -1)
        {
          result = ERANGE;
        }
      }
      else if ((url[index] == '/') && (url[index+1] == '/'))
      {
        // Main separator
        if ((pathSep == -1) && (mainSep == -1))
        {
          mainSep = index;
            
          if (separator != -1)
          {
            [_scheme set :url :start :(separator-1)];
            
            separator = -1;
          }
          start = index + 2;
        }
        index++;
      }
      else if (url[index] == '/')
      {
        // Path separator
        if (pathSep == -1)
        {
          pathSep = index;
          
          if (separator != -1)
          {
            char *port = (char *) (url+(separator+1));
              
            [_host set :url :start :(separator-1)];
              
            [_port fromString :&port];
            
            separator = -1;
          }
          else if (start != index)
          {
            [_host set :url :start :end]; // and no port ..
          }
          
          start = pathSep + 1;
        }
      }
      else if (url[index] == '@')
      {
        // User separator
        if ((userSep == -1) && (pathSep == -1))
        {
          userSep = index;
          
          if (separator != -1)
          {
            _noPassword = NO;
            
            [_user     set :url :start         :(separator-1)];
            [_password set :url :(separator+1) :end          ];
            
            separator = -1;
          }
          else
          {
            [_user     set :url :start :end]; // and no password ..
          }
          _noUser = NO;
          
          start = userSep + 1;
        }
      }
      end = index++;
    }
    
    if (pathSep != -1)
    {
      [_path set :url :pathSep :end];
    }
    else // no path, fill in host..
    {
      if (separator != -1)
      {
        char *port = (char *) (url+(separator+1));
        
        [_host set :url :start :(separator-1)];
        
        [_port fromString :&port];
      }
      else if (start != index)
      {
        [_host set :url :start :end]; // and no port ..
      }
    }
  }
  *cstr = *cstr + index;
  
  return result;
}


//// Textable protocol methods

//
// Convert the url object to a text object
//
// @return the (new) text string object
//
- (DText *) toText
{
  return [self url];
}

@end

/*===========================================================================*/

